#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>

#include "vm.h"
#include "dbg.h"


/*Loads a program into the brain10 vm. Once started,
 the instructions and data are loaded into memory.
 @param *vm pointer to a brain10 vm struct
 @param fileName the name of the file to load
 */
FILE* loadProgram(brain10 *vm, char fileName[], list* rdyQ) {



	/* Find and open the file */
	FILE* file = fopen(fileName, "r");
	if (!file) {
		printf("Could not open file\n");
		exit(1);
	}

	/* the first line should be BRAIN10 */
	char buffer[100];
	fgets(buffer, 100, file);
	if (strncmp(buffer, "BRAIN10", 7)) {
		printf("The file format is not correct\n");
		printf("First line is %s, should be BRAIN10\n", buffer);
		exit(1);
	}

	/* all lines up until DATA are instructions. Read
	 them into memory */
	int pid = 1;
	int ic = 0; //instructions counted so far
	strcpy(vm->BR, "0000");
	strcpy(vm->LR, "0099");
	fgets(buffer, 100, file);
	while (strncmp(buffer, "DATA", 4) != 0) {

		if(strncmp(buffer, "BRAIN10", 7) == 0){
			fgets(buffer, 100, file);
			snprintf(vm->PID, 3, "%.2i", pid);

			pcb* block = (pcb*) malloc(sizeof(pcb));
			storeContext(block, vm);  //store the process context in a pcb
			append(rdyQ, block, 0);  //append that process context to the rdyQ

			reset(vm); //clear the current values of the registers
//			printf("End of process %i\n",pid);
			ic = calcBR(ic);
			pid++;

			snprintf(vm->BR, 5, "%.4i", ic);  //set up the BR for the next process
			snprintf(vm->LR, 5, "%.4i", ic+99);

//			printf("Starting at memory loc %i for process %i, with instruction %s\n",ic,pid,buffer);

		}

		strncpy(vm->memory[ic], buffer, 4);
		fgets(buffer, 100, file);
		ic++;
	}

	snprintf(vm->PID, 3, "%.2i", pid);

	pcb* block = (pcb*) malloc(sizeof(pcb));
	storeContext(block, vm);  //store the process context in a pcb
	append(rdyQ, block, 0);  //append that process context to the rdyQ

	reset(vm); //clear the current values of the registers
	ic = calcBR(ic);
	pid++;

	snprintf(vm->BR, 5, "%.4i", ic);  //set up the BR for the next process
	snprintf(vm->LR, 5, "%.4i", ic+99);

	return file;
}

/*store the context of a brain10 vm into a
 * skeleton process control block.
 * @param block The pcb to saved the process context into
 * @param vm The virtual machine to save the context of
 */
void storeContext(pcb* block, brain10* vm){
	strcpy(block->R, vm->R); //copy the register
	strcpy(block->IC, vm->IC); //copy the counter
	strcpy(block->SP, vm->SP); //copy the stack pointer
	strcpy(block->BR, vm->BR); //copy the base register
	strcpy(block->LR, vm->LR); //copy the limit register
	block->C = vm->C;
	strcpy(block->PID, vm->PID); //copy the process id
}

/*load the context of a process control block
 * into a brain10 vm
 * @param block The pcb to load from
 * @param vm The virtual machine to load into
 */
void loadContext(brain10* vm, pcb* block){
	strcpy(vm->R, block->R); //copy the register
	strcpy(vm->IC, block->IC); //copy the counter
	strcpy(vm->SP, block->SP); //copy the stack pointer
	strcpy(vm->BR, block->BR); //copy the base register
	strcpy(vm->LR, block->LR); //copy the limit register
	vm->C = block->C;
	strcpy(vm->PID, block->PID); //copy the process id
}

/*zero out all of the registers in a vm
 * @param vm The virtual machine to reset
 */
void reset(brain10*vm){
	sprintf(vm->R, "0000");
	sprintf(vm->IC, "00");
	sprintf(vm->SP, "0000");
	sprintf(vm->BR, "0000");
	sprintf(vm->LR, "0000");
	sprintf(vm->PID, "00");
	vm->C = '-';
}

int calcBR(int current){
	double d = current;
	d /= 100;
	current = (int)d;
	current++;
	current *= 100;

	return current;
}

/* project 3
 * Initialize the value of all of the semaphores
 */
void initSemaphores(brain10* vm, int value){
	int i;
	for(i=0; i < 100; i++){
		vm->semaphores[i].count = value;
	}
}

/*Dump the contents of the register to the screen
 @param *vm pointer to a brain10 vm struct
 @param n max words to print
 */
void print(brain10 *vm) {
	/*print out the registers*/

	printf("R   :    %s        ", vm->R);
	printf("SP  :    %s        ", vm->SP);
	printf("IC  :    %s\n", vm->IC);
	printf("BR  :    %s        ", vm->BR);
	printf("LR  :    %s\n", vm->LR);
	printf("C   :    %c        ", vm->C);
	printf("PID :    %s\n\n", vm->PID);

}

/*load instruction
 @param *vm pointer to a virutal machine struct
 @param address address in memory between 0 and 99
 */
void LR(brain10 *vm, int address) {
	strncpy(vm->R, vm->memory[address+atoi(vm->BR)], 4);
}

/*load instruction, low order bits
 @param *vm pointer to a virutal machine struct
 @param address address in memory between 0 and 99
 */
void LL(brain10 *vm, int address) {
	int i;
	for (i = 2; i < 4; i++) {
		vm->R[i] = vm->memory[address+atoi(vm->BR)][i];
	}
}

/*load instruction, high order bits
 @param *vm pointer to a virutal machine struct
 @param address address in memory between 0 and 99
 */
void LH(brain10 *vm, int address) {
	int i;
	for (i = 0; i < 2; i++) {
		vm->R[i] = vm->memory[address+atoi(vm->BR)][i];
	}
}

/*Copy the contents of the register into
 address.
 @param *vm pointer to a virutal machine struct
 @param address address in memory between 0 and 99
 */
void SR(brain10 *vm, int address) {
	strncpy(vm->memory[address+atoi(vm->BR)], vm->R, 4);
}

/*Copy the contents of the register into SP
 @param *vm pointer to a virutal machine struct
 */
void SP(brain10 *vm) {
	strncpy(vm->SP, vm->R, 4);
}

/*Copy the contents of SP into the register
 @param *vm pointer to a virutal machine struct
 */
void PS(brain10 *vm) {
	strncpy(vm->R, vm->SP, 4);
}

/*Increment SP, then set the contents of the address
 SP contains to the contents of R
 @param *vm pointer to a virutal machine struct
 */
void PH(brain10* vm) {
	int sp = atoi(vm->SP);
	sp++;
	snprintf(vm->SP, 5, "%.4i", sp); //ADDED .4, changed 5

	strncpy(vm->memory[sp+atoi(vm->BR)], vm->R, 4);
}

/*Set R to the contents of the address SP contains,
 then decrement SP.
 @param *vm pointer to a virutal machine struct
 */
void PP(brain10* vm) {
	int sp = atoi(vm->SP);

	strncpy(vm->R, vm->memory[sp+atoi(vm->BR)], 4);

	sp--;
	snprintf(vm->SP, 4, "%i", sp);
}

/*If R is equal to the contents of address
 then set C to 'T', else 'F'
 @param *vm pointer to a virutal machine struct
 @param address address in memory between 0 and 99
 */
void CE(brain10 *vm, int address) {

	if (strncmp(vm->R, vm->memory[address+atoi(vm->BR)], 4)) {
		vm->C = 'F';
	} else {
		vm->C = 'T';
	}
}

/*If R is less than the contents of address
 then set C to 'T', else 'F'
 @param *vm pointer to a virutal machine struct
 @param address address in memory between 0 and 99
 */
void CL(brain10 *vm, int address) {

	int i, j;
	i = atoi(vm->R);
	j = atoi(vm->memory[address+atoi(vm->BR)]);

	if (i < j) {
		vm->C = 'T';
	} else {
		vm->C = 'F';
	}
}

/*If C is 'T' then IC is set to arg
 @param *vm pointer to a virutal machine struct
 @param arg two byte array that contains the arguments
 x1x2 in the initial instruction.
 */
int BT(brain10 *vm, char arg[]) {
	if (vm->C == 'T') {
		strncpy(vm->IC, arg, 3);
		return 1;
	}
	return 0;
}

/*Set IC to arg
 @param *vm pointer to a virutal machine struct
 @param arg two byte array that contains the arguments
 x1x2 in the initial instruction.
 */
void BU(brain10 *vm, char arg[]) {
	strncpy(vm->IC, arg, 3);
}

/*Reads the next line from the input stream.
 @param *vm pointer to a brain10 vm struct
 @param arg two byte array that contains the arguments
 x1x2 in the initial instruction
 */
void GD(brain10 *vm, int address, FILE *file) {
	char buffer[100], temp[5]; //I changed temp to a char instead of an int so that the
	int counter = 0, c, i; //the strncpy would stop throwing warnings at us
	fgets(buffer, 100, file);

	for (c = address; c < address + 10; c++) {
		if (c > 99) {
			printf("ERROR: memory exceeded 99\n");
			exit(1);
		}
		for (i = 0; i < 5; i++) {
			temp[i] = buffer[counter];
			counter++;
		}
		strncpy(vm->memory[c+atoi(vm->BR)], temp, 5);
		vm->memory[c+atoi(vm->BR)][4] = '\0';
	}
}

/*Print the contents of memory 10 at a time.
 @param *vm pointer to a brain10 vm struct
 @param start the location to start printing
 */
void PD(brain10 *vm, int start) {

	printf("\nPROC %s \n%.4i:  ",vm->PID,start);
	int i;
	for (i = start; i < start + 10; i++) {
		printf("%s  ", vm->memory[i+atoi(vm->BR)]);
	}
	printf("\n\n");

}

/*Add the contents in the register and the memory at
 paramater location 'address' and then store it back
 into the Register
 @param *vm pointer to a brain10 vm struct
 @param address address in memory between 0 and 99
 */
void AD(brain10 *vm, int address) {

	int r, a;
	a = atoi(vm->memory[address+atoi(vm->BR)]);
	r = atoi(vm->R);
	a += r;
	snprintf(vm->R, 5, "%.4i", a);
}

/*Subtract the contents of the memory at the address
 location only if the Register is larger than the
 value in the memory and load that back into the Register
 @param *vm pointer to a brain10 vm struct
 @param address address in memory between 0 and 99
 */
void SU(brain10 *vm, int address) {

	int r, a;
	a = atoi(vm->memory[address+atoi(vm->BR)]);
	r = atoi(vm->R);
	if (r >= a) {
		r = r - a;
		snprintf(vm->R, 5, "%.4i", r);
	}
}

/*Multiply the contents in the register and the memory at
 paramater location 'address' and then store it back
 into the Register
 @param *vm pointer to a brain10 vm struct
 @param address address in memory between 0 and 99
 */
void MU(brain10 *vm, int address) {

	int r, a;
	a = atoi(vm->memory[address+atoi(vm->BR)]);
	r = atoi(vm->R);
	a *= r;
	snprintf(vm->R, 5, "%.4i", a);
}

/*Divide the contents in the register by the value in memory at
 paramater location 'address' and then store it back
 into the Register
 @param *vm pointer to a brain10 vm struct
 @param address address in memory between 0 and 99
 */
void DI(brain10 *vm, int address) {

	int r, a;
	a = atoi(vm->memory[address+atoi(vm->BR)]);
	r = atoi(vm->R);
	if (a > 0) {
		r = r / a;
		snprintf(vm->R, 5, "%.4i", r);
	}
}

//Add Stack
void AS(brain10 *vm) {

	//	int Temp = atoi(vm->SP); //wrong
	//	sprintf(vm->SP, "%d",atoi(vm->SP)-1);
	//	sprintf(vm->SP, "%d",atoi(vm->SP)+Temp);

	int Temp = atoi(vm->memory[atoi(vm->SP)+atoi(vm->BR)]);
	sprintf(vm->SP, "%.4i", atoi(vm->SP) - 1);
	sprintf(vm->memory[atoi(vm->SP)+atoi(vm->BR)], "%.4i",
			atoi(vm->memory[atoi(vm->SP)+atoi(vm->BR)]) + Temp);

}

//Subtract Stack
void SS(brain10 *vm) {
	int Temp = atoi(vm->memory[atoi(vm->SP)+atoi(vm->BR)]);
	sprintf(vm->SP, "%.4i", atoi(vm->SP) - 1);
	sprintf(vm->memory[atoi(vm->SP)+atoi(vm->BR)], "%.4i",
			atoi(vm->memory[atoi(vm->SP)+atoi(vm->BR)]) - Temp);
}

//Multiply Stack
void MS(brain10 *vm) {
	int Temp = atoi(vm->memory[atoi(vm->SP)+atoi(vm->BR)]);
	sprintf(vm->SP, "%.4i", atoi(vm->SP) - 1);
	sprintf(vm->memory[atoi(vm->SP)+atoi(vm->BR)], "%.4i",
			atoi(vm->memory[atoi(vm->SP)+atoi(vm->BR)]) * Temp);
}

//Divide Stack
void DS(brain10 *vm) {
	int Temp = atoi(vm->memory[atoi(vm->SP)+atoi(vm->BR)]);
	sprintf(vm->SP, "%.4i", atoi(vm->SP) - 1);
	sprintf(vm->memory[atoi(vm->SP)+atoi(vm->BR)], "%.4i",
			atoi(vm->memory[atoi(vm->SP)+atoi(vm->BR)]) / Temp);
}

/* Helper function for the SD and RC instructions that
 * copies memory from one location to another based on
 * the BR and R of two pcb's / nodes.
 * @param vm the virtual machine where the memory resides
 * @param to the node containing the PCB that will be written to
 * @param from the node containing hte PCB that will be
 * read from
 * @param size the amount of words to copy
 */
void copy(brain10* vm, node* to, node* from, int size){

	int toR = atoi(to->data->R);
	int toBR = atoi(to->data->BR);
	int fromR = atoi(from->data->R);
	int fromBR = atoi(from->data->BR);

	int i;
	for(i = 0; i < size; i++){
		strcpy(vm->memory[toR+toBR+i], vm->memory[fromR+fromBR+i]);
	}

}


int SD(brain10* vm, list* sndQ, list* rcvQ, list* rdyQ, char targetPID []){

#ifdef DEBUG
	printf("CALLED SD\n");
#endif

	/* list of processes currently waiting to
	   receive a message from the sender    */
	list* q = &rcvQ[atoi(rdyQ->head->data->PID)];
	// check to see if anyone is waiting for this process to send
	int j;
	for(j = 0; j < 2; j++){

		if(q->length > 0){

			node* n = q->head;
			int i;
			for(i = 0; i < q->length; i++){
				//see if any of them are the right ones to send to
				if( strcmp(n->data->PID, targetPID) == 0 ){
#ifdef DEBUG
					printf("found process, sending\n");
#endif
					int sndR = atoi(rdyQ->head->data->R);
					int sndBR = atoi(rdyQ->head->data->BR);
					int rcvR = atoi(n->data->R);
					int rcvBR = atoi(n->data->BR);

					int j;
					for(j = 0; j < 10; j++){
						strcpy(vm->memory[rcvR+rcvBR+j], vm->memory[sndR+sndBR+j]);
					}



					//remove the receiver from his queue
					append(rdyQ, 0, rm(q, i));

					//				cycle(rdyQ);
					return 0;
				}
				n = n->next; //compare with the next process in the queue
			}

		}
		/* check the 'receive from all XX' queue, which is
		 * the 0 mailbox. Makes sure that processes that are waiting
		 * to receive specifically from this process are prioritized
		 */
		q = &rcvQ[0];

	}

#ifdef DEBUG
	printf("putting process %s into %s sndQ list \n", rdyQ->head->data->PID,targetPID);
#endif
	append(&sndQ[atoi(targetPID)], 0, pop(rdyQ) );
	return 1;
}

/* Receive a message from another process
 * @param vm A brain10 vm. Needed in order to copy
 * the message back into memory
 * @param sndQ An array of list types (defined in queue.h).
 * Each list represents a 'mailbox' for the process that corresponds
 * to the index it resides in. So sndQ[2] contains a list used
 * for managing every process that is currently waiting to send to
 * process two.
 * @param rcvQ An array of list types (defined in queue.h).
 * Each list represents a 'mailbox' for the process that corresponds
 * to the index it resides in. So rcvQ[1] contains a list used for
 * managing every process currently waiting to receive from p1
 * @param rdyQ A list of all currently ready and executing processes.
 * @param targetPID char array that contains the target process to
 * receive from, or XX if any process.
 */
int RC(brain10* vm, list* sndQ, list* rcvQ, list* rdyQ, char targetPID []){

#ifdef DEBUG
	printf("CALLED RC at %s\n",targetPID);
#endif

	/* list of processes currently waiting to
	   send a message to the receiver    */
	list* q = &sndQ[atoi(rdyQ->head->data->PID)];


	// check to see if anyone is trying to send to this process
	if(q->length > 0){

		node* n = q->head;
		int i;
		for(i = 0; i < q->length; i++){

			//see if any of them are the right ones to receive from
			if( strcmp(n->data->PID, targetPID) == 0 || strcmp(targetPID, "XX") == 0){
#ifdef DEBUG
				printf("matched %s with %s, i=%i\n",targetPID, n->data->PID,i);
#endif
				copy(vm, rdyQ->head, n, 10);

				//remove the sender from his queue
				append(rdyQ,  0, rm(q, i));
				//					cycle(rdyQ);
				return 0;
			}
			n = n->next; //compare with the next process in the queue
		}
	}

	if( strncmp(targetPID, "XX",2) == 0 ){
#ifdef DEBUG
		printf("receiving from all, throwing into XX queue\n");
#endif
		append(&rcvQ[0], 0, pop(rdyQ) );
		return 1;
	}else{
		append(&rcvQ[atoi(targetPID)], 0, pop(rdyQ) );
		return 1;
	}

}

/* Loads the process ID into R
 * @param vm A brain10 vm to load the ID into
 */
void GP(brain10* vm){
	vm->R[0] = '0';
	vm->R[1] = '0';
	vm->R[2] = vm->PID[0];
	vm->R[3] = vm->PID[1];
}


/* Load Shared  -  project 3
 * loads memory from the shared memory segment
 * into the register
 * @param *vm pointer to a brain10 vm struct
 * @param address address in memory between 0 and 99
 */
void LS(brain10* vm, int address){
	strncpy(vm->R, vm->shared[address], 4);
}


/* Store Shared  - project 3
  * stores data from the register
  * into the shared memory segment
  * @param *vm pointer to a brain10 vm struct
  * @param address address in memory between 0 and 99
  */
void ST(brain10* vm, int address){
	strncpy(vm->shared[address], vm->R, 4);
}

/* added in project 3
 * print values from shared memory
 */
void printShared(brain10 *vm, int start) {

	printf("\nPROC %s \n%.4i:  ",vm->PID,start);
	int i;
	for (i = start; i < start + 10; i++) {
		printf("%s  ", vm->shared[i]);
	}
	printf("\n\n");

}

/* Wait  - poject 3
 * calls wait() on the semaphore specified
 * in the address field
 * @param *vm pointer to a brain10 vm struct
 * @param address address in memory between 0 and 99
 */
int PE(brain10* vm, int address){
	return wait(&vm->semaphores[address]);
}

/* Signal  - project 3
 * calls signal() on the semaphore specified
 * in the address field
 * @param *vm pointer to a brain10 vm struct
 * @param address address in memory between 0 and 99
 */
int VE(brain10* vm, int address){
	sig(&vm->semaphores[address]);
	return 0;
}

/* Semaphore Initialize  -  project 3
 * initializes a semaphore value
 * @param *vm pointer to a brain10 vm struct
 * @param address address in memory between 0 and 99
 */
void SI(brain10* vm, int address){
	vm->semaphores[address].count = address;
}

/* added in project 3
 * this function needs to be called in order to use
 * semaphores. It tells the semaphores which ready queue
 * they are receiving waits and signals from, and where
 * processes should be placed when they are removed from a
 * semaphore queue.
 * @param readyQ The queue that semaphores return processes
 * to.
 */
void setReadyQ(list* readyQ){
	process_ready_queue = readyQ;
}
